// js
"use strict";

// 从 main 函数开始看

// 创建着色器 shader。gl：WebGL 上下文；type：着色器类型；source：着色器文本
function createShader(gl, type, source) {
    // 根据 type 创建着色器
    var shader = gl.createShader(type);
    // 绑定内容文本 source
    gl.shaderSource(shader, source);
    // 编译着色器（将文本内容转换成着色器）
    gl.compileShader(shader);
    // 获取编译后的状态
    var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (success) {
        return shader;
    }

    // 获取当前着色器相关信息
    console.log(gl.getShaderInfoLog(shader));
    // 删除失败的着色器
    gl.deleteShader(shader);
}

// 创建着色程序 program。gl：WebGL 上下文；vertexShader：顶点着色器对象；fragmentShader：片元着色器对象
function createProgram(gl, vertexShader, fragmentShader) {
    // 创建着色程序
    var program = gl.createProgram();
    // 让着色程序获取到顶点着色器
    gl.attachShader(program, vertexShader);
    // 让着色程序获取到片元着色器
    gl.attachShader(program, fragmentShader);
    // 将两个着色器与着色程序进行绑定
    gl.linkProgram(program);
    var success = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (success) {
        return program;
    }

    console.log(gl.getProgramInfoLog(program));
    // 绑定失败则删除着色程序
    gl.deleteProgram(program);
}

function main() {
    // 步骤一：获取 gl

    // 创建画布
    const canvas = document.createElement('canvas');
    canvas.style.border = '1px solid';
    document.getElementsByTagName('body')[0].appendChild(canvas);
    canvas.width = 400;
    canvas.height = 300;
    // 获取 WebGL 上下文（Context），后续统称 gl。
    const gl = canvas.getContext("webgl");
    if (!gl) {
        console.error("获取 WebGL 上下文失败！");
        
        return;
    }

    // 步骤二：顶点着色器

    // 定义顶点着色器文本
    const vertexShaderSource = `
        // 接收顶点位置数据
        attribute vec2 a_position;
        // 着色器入口函数
        void main() {
            // gl_Position 接收的就是一个 vec4，因此需要转换
            gl_Position = vec4(a_position, 0.0, 1.0);
        }`;
    // 根据着色器文本内容，创建 WebGL 上可以使用的着色器对象
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    // 自定义裁剪坐标。还是以画三角形为例提供顶点数据。因为是一个平面三角形，因此每一个顶点只提供一个 vec2 即可。
    const positions = [
        0, 0,
        0, 0.5,
        0.7, 0,
    ];
    // 创建顶点缓冲对象
    const vertexBuffer = gl.createBuffer();
    // 将顶点缓冲对象绑定到 gl 的 ARRAY_BUFFER 字段上。
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    // 通过 bufferData 将当前顶点数据存入缓冲（vertexBuffer）中
    // 之前有说过，gl 内部有很多默认状态，所以此时需要明确储存的顶点缓冲是我自定义的缓冲。在 GPU 中数据存储需要很谨慎，不然就有可能造成内存浪费。
    // 接着，需要明确数据大小，这里顶点坐标的每一个分量都采用 32 位浮点型数据存储。需要合理的为每一类型数据分配内存。
    // 最后一个参数 gl.STATIC_DRAW 是提示 WebGL 我们将怎么使用这些数据。因为此处我们将顶点数据写死了，所以采用 gl.STATIC_DRAW。
    // gl.STATIC_DRAW ：数据不会或几乎不会改变。
    // gl.DYNAMIC_DRAW：数据会被改变很多。
    // gl.STREAM_DRAW ：数据每次绘制时都会改变
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    // 步骤三：片元着色器

    // 同顶点着色器操作类似
    // 获取片元着色器文本
    const fragmentShaderSource = `
        precision mediump float;
        // 着色器入口函数
        void main() {
            // 将三角形输出的最终颜色固定为玫红色
            // 这里的四个分量分别代表红（r）、绿（g）、蓝（b）和透明度（alpha）
            // 颜色数值取归一化值。最终绘制的其实就是 [255, 0, 127. 255]
            gl_FragColor = vec4(1, 0, 0.5, 1);
        }`;
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

    // 步骤四：着色程序 

    // 将顶点着色器和片元着色器绑定到着色程序上。
    // 这个上一章提过，着色程序需要成对提供，其中一个是顶点着色器，另一个是片元着色器
    const program = createProgram(gl, vertexShader, fragmentShader);
        // 步骤五：处理绘制的前置工作
    
    // 设置视口尺寸，将视口和画布尺寸同步
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 
    // 清除画布颜色，直接设置成透明色。此处是为了便于观察，将它设置成黑色。
    // 注意，渲染管线是每帧都会绘制内容，就好比每帧都在画板上画画，如果不清除的话，就有可能出现花屏现象
    gl.clearColor(0, 0, 0, 255);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    // 步骤六：启动程序，启用顶点属性
    
    // 启用我们当前需要的着色程序
    gl.useProgram(program);
    // 查询顶点要去的地方，并启用属性
    const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
    gl.enableVertexAttribArray(positionAttributeLocation);  
    // 将顶点缓冲绑定到当前数据缓冲接口上，这样后续操作的缓冲都是当前绑定的缓冲。每次需要使用数据的时候都要绑定一次。
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

    // 步骤七：告诉属性如何获取数据
      
    // gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset); 
    // positionAttributeLocation：获取顶点着色器上的 “a_position” 属性的位置
    // size：当前一个顶点数据里要取的数据长度，因为绘制的是平面三角形，所以位置只需提供 x，y 即可，所以数量是 2     
    // type：数据缓冲类型，此处顶点采用的是 float 32，因此使用 gl.FLOAT
    // normalize：数据是否是归一化的数据，通常不用
    // stride：主要表达数据存储的方式，单位是字节。0 表示属性数据是连续存放的，通常在只有一个属性的数据里这么用
    // 非 0 则表示同一个属性在数据中的间隔大小，可以理解为步长。这个会在后面的说明中体现
    // offset：属性在缓冲区中每间隔的偏移值，单位是字节
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
    
    // 步骤八：绘制
    
    // gl.drawArrays(primitiveType, offset, count);
    // primitiveType：指定图元绘制形式，常见的为绘制点（gl.POINTS），线（gl.LINE_STRIP）和三角面（gl.TRIANGLES）。此处绘制三角形。
    // offset：从哪个点开始绘制
    // count：本次绘制会使用到多少个点。也代表顶点着色器需要运行几次。顶点着色器每次只处理一个顶点。
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    
    // 到此处为止就正式启动了绘制的流程了，后续的处理就交给顶点着色器和片段着色器了 
}

main()

